home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / lisp / elk-2_0.lha / elk-2.0 / examples / scheme / Y < prev   
Encoding:
Text File  |  1989-02-17  |  5.8 KB  |  174 lines

  1. ; Date: 15 Nov 88 23:03:24 GMT
  2. ; From: uoregon!markv@beaver.cs.washington.edu  (Mark VandeWettering)
  3. ; Organization: University of Oregon, Computer Science, Eugene OR
  4. ; Subject: The Paradoxical Combinator -- Y (LONG)
  5. ; Alternatively entitled:
  6. ;     "Y?  Why Not?" :-)
  7. ; The discussion that has been going on in regards to the Y combinator as
  8. ; the basic operation in implementing recursive functions are interesting.
  9. ; The practical tests that people have made have shown that the Y
  10. ; combinator is orders of magnitude slower for implementing recursion than
  11. ; directly compiling it.
  12. ; This is true for Scheme.  I hold that for an interesting set of
  13. ; languages, (lazy languages) that this result will not necessarily hold.
  14. ; The problem with Y isn't its complexity, it is the fact that it is an
  15. ; inherently lazy operation.  Any implementation in Scheme is clouded by
  16. ; the fact that Scheme is an applicative order evaluator, while Y prefers
  17. ; to be evaluated in normal order.
  18. ;     
  19.  (define Y
  20.    (lambda (g)    
  21.      ((lambda (h) (g (lambda (x) ((h h) x))))
  22.       (lambda (h) (g (lambda (x) ((h h) x)))))))
  23.  (define fact
  24.    (lambda (f)
  25.      (lambda (n)
  26.        (if (= n 1)
  27.        1
  28.      (* n (f (- n 1)))))))
  29. ; Evaluating (Y fact) 2 results in the following operations in
  30. ; Scheme:
  31. ; The argument is (trivially) evaluated, and returns two.
  32. ; (Y fact) must be evaluated.  What is it?  Y and fact each evaluate
  33. ; to closures.  When applied, Y binds g to fact, and executes the
  34. ; body.
  35. ; The body is an application of a closure to another closure.  The
  36. ; operator binds h to the operand, and executes its body which....
  37. ; Evaluates (g (lambda (x) ((h h) x))).  The operand is a closure,
  38. ; which gets built and then returns.  g evaluates to fact.  We
  39. ; substitute the closure (lambda (x) ((h h) x)) in for the function
  40. ; f in the definition of fact, giving...
  41. ; (lambda (n)
  42. ;   (if (= n 1) 
  43. ;       1
  44. ;     (* n ((lambda (x) ((h h) x)) (- n 1)))))
  45. ; Which we return as the value of (Y fact).  When we apply this to 2, we get
  46. ; (* 2 ((lambda (x) ((h h) x)) 1))
  47. ; We then have to evaluate
  48. ; ((lambda (x) ((h h) x)) 1)
  49. ; or 
  50. ; ((h h) 1)
  51. ; But remembering that h was (lambda (h) (g (lambda (x) ((h h) x)))), 
  52. ; we have 
  53. ; (((lambda (h) (g (lambda (x) ((h h) x))))
  54. ;   (lambda (h) (g (lambda (x) ((h h) x)))))
  55. ;  1) ....
  56. ; So, we rebind h to be the right stuff, and evaluate the body, which is
  57. ; ((g (lambda (x) ((h h) x))) 1)
  58. ; Which by the definition of g (still == fact) is just 1.
  59. ; (* 2 1) = 2.
  60. ; ########################################################################
  61. ; Summary:  If you didn't follow this, performing this evaluation
  62. ; was cumbersome at best.  As far as compiler or interpreter is
  63. ; concerned, the high cost of evaluating this function is related
  64. ; to two different aspects:
  65. ; It is necessary to create "suspended" values.  These suspended
  66. ; values are represented as closures, which are in general heap
  67. ; allocated and expensive.
  68. ; For every level of recursion, new closures are created (h gets
  69. ; rebound above).  While this could probably be optimized out by a
  70. ; smart compiler, it does seem like the representation of suspended
  71. ; evaluation by lambdas is inefficient.
  72. ;        
  73. ; ########################################################################
  74. ; You can try to figure out how all this works.  It is complicated, I
  75. ; believe I understand it.  The point in the derivation above is that in
  76. ; Scheme, to understand how the implementation of Y works, you have to
  77. ; fall back on the evaluation mechanism of Scheme.  Suspended values must
  78. ; be represented as closures.  It is the creation of these closures that
  79. ; cause the Scheme implementation to be slow.
  80. ; If one wishes to abandon Scheme (or at least applicative order
  81. ; evaluators of Scheme) one can typically do much better.  My thesis work
  82. ; is in graph reduction, and trying to understand better the issues having
  83. ; to do with implementation.
  84. ; In graph reduction, all data items (evaluated and unevaluated) have the
  85. ; same representation: as graphs in the heap.  We choose to evaluate using
  86. ; an outermost, leftmost strategy.  This allows the natural definition of
  87. ; (Y h) = (h (Y h)) to be used.  An application node of the form:
  88. ;                 @
  89. ;                / \
  90. ;               /   \
  91. ;              Y     h
  92. ; can be constructed in the obvious way:
  93. ;                             @
  94. ;                / \
  95. ;               /   \
  96. ;              h     @
  97. ;                   /    \
  98. ;                  /   \
  99. ;                 Y     h
  100. ; costing one heap allocation per level of recursion, which is
  101. ; certainly cheaper than the multiple allocations of scheme
  102. ; closures above.  More efficiently, we might choose to implement
  103. ; it using a "knot tying" version:
  104. ;                   /\
  105. ;                              /     \
  106. ;                 @     |
  107. ;                / \     /
  108. ;               /   \/
  109. ;              h
  110. ; Which also works quite well.  Y has been eliminated, and will
  111. ; cause no more reductions.  
  112. ; The basic idea is somehow that recursion in functional languages
  113. ; is analogous to cycles in the graph in a graph reduction engine.
  114. ; Therefore, the Y combinator is a specific "textual" indicator of
  115. ; the graph.
  116. ; The G-machine (excellently described in Peyton Jones' book "The
  117. ; Implementation of Functional Programming Languages") also
  118. ; described the Y combinator as being efficient.  He chose letrecs
  119. ; as being a primitive in the extended lambda calculus.  His
  120. ; methodology behind compiling these recursive definitions was
  121. ; basically to compile fixed code which directly built these cyclic
  122. ; structures, rather than having them built at runtime.
  123. ; I think (and my thesis work is evolving into this kind of
  124. ; argument) that Y is overlooked for trivial reasons.  Partial
  125. ; evaluation and smarter code generation could make an SK based
  126. ; compiler generate code which is equal in quality to that produced
  127. ; by supercombinator based compilation.
  128. ; This is too long already, ciao for now.
  129. ; Mark VandeWettering
  130.  
  131. (print ((Y fact) 10))
  132.